package gps.implementationAPI;

import gps.Colors;
import gps.Direction;
import gps.GPSRun;
import gps.api.GPSState;
import gps.states.SquareState;

import java.awt.Point;
import java.lang.reflect.Array;
import java.util.Arrays;


/**
 * 
 * Esta clase representa el estado de un problema. El mismo esta compuesto por 8 celdas de tipo
 * SquareState y un blanco.
 * El mismo se representa con una lista de 9 elementos donde el elemento con null representa el
 * espacio vacío, aunque para quien lo invoque se trata como una matriz de 3x3 de la siguiente manera:
 * 
 * 1 2 3
 * 4 5 6
 * 7 8 9
 * 
 * @author grupo2
 *
 */
public class GPSStateImplementation implements GPSState {
	private SquareState[] state;
	
	/**
	 * Funcion que crea un estado goal, todos sus estados en blanco y el punto donde queda
	 * el espacio vacio
	 * @param blank punto donde va a quedar el espacio vacio
	 * @return
	 */
	public static GPSStateImplementation getOneGoalState (Point blank) {
		if (blank.x < 0 || blank.x >= GPSRun.N || blank.y < 0 || blank.y >= GPSRun.N) {
			return null;
		}
		GPSStateImplementation gpsState = new GPSStateImplementation();
		SquareState elementState = new SquareState(Colors.WHITE, Colors.WHITE);
		for (int i = 0; i < GPSRun.N; i++) {
			for (int j = 0; j < GPSRun.N; j++) {
				gpsState.setElementState(i, j, elementState);
			}
		}
		gpsState.setElementState(blank.x, blank.y, null);
		return gpsState;
	} 
	
	/**
	 * El constructor me genera el estado inicial del problema
	 */
	public GPSStateImplementation() {
		this.state = new SquareState[GPSRun.N*GPSRun.N];
		for (int i = 0; i < GPSRun.N*GPSRun.N; i++) {
			if (i != (GPSRun.N*GPSRun.N)/2) {
				this.state[i] = new SquareState(Colors.BLACK, Colors.BLACK);
			}
		}
		this.state[(GPSRun.N*GPSRun.N)/2] = null;
	}
	
//	public GPSStateImplementation() {
//		this.state = new SquareState[GPSRun.N*GPSRun.N];
//		for (int i = 0; i < GPSRun.N*GPSRun.N; i++) {
////			if (i != (GPSRun.N*GPSRun.N)/2) {
//				this.state[i] = new SquareState(Colors.WHITE, Colors.WHITE);
////			}
//		}
//		this.state[7] = null;
//		this.state[8] = new SquareState(Direction.VERTICAL, Colors.BLACK, Colors.WHITE);
//		this.state[5] = new SquareState(Direction.HORIZONTAL, Colors.WHITE, Colors.BLACK);
//	}
	
//	public GPSStateImplementation() {
//	this.state = new SquareState[GPSRun.N*GPSRun.N];
//	this.state[0] = new SquareState(Direction.NONE, Colors.WHITE, Colors.WHITE);
//	this.state[1] = null;
//	this.state[2] = new SquareState(Direction.VERTICAL, Colors.BLACK, Colors.WHITE);
//	this.state[3] = new SquareState(Direction.NONE, Colors.WHITE, Colors.WHITE);
//	this.state[4] = new SquareState(Direction.HORIZONTAL, Colors.WHITE, Colors.BLACK);
//	this.state[5] = new SquareState(Direction.HORIZONTAL, Colors.BLACK, Colors.WHITE);
//	this.state[6] = new SquareState(Direction.NONE, Colors.WHITE, Colors.WHITE);
//	this.state[7] = new SquareState(Direction.VERTICAL, Colors.WHITE, Colors.BLACK);
//	this.state[8] = new SquareState(Direction.HORIZONTAL, Colors.BLACK, Colors.WHITE);
//}
	
	
	public GPSStateImplementation(SquareState[] states){
		if(states.length != GPSRun.N*GPSRun.N)
			throw new IllegalArgumentException("states tiene que tener tamanio " + (GPSRun.N*GPSRun.N) + " y tener un espacio vaci�");
		int count = 0;
		for(SquareState s : states){
			if(s == null){
				count++;
			}
		}
		
		if(count != 1) {
			throw new IllegalArgumentException("states tiene que tener un espacio vacio.");
		}
		
		this.state = states;
	}
	
	/**
	 * Setea el elemento element en la posicion (i,j)
	 * Lanza IllegalArgumentException si la coordenada no es valida
	 * 
	 * @param i
	 * 			Fila de la matriz
	 * @param j
	 * 			Columna de la matriz
	 * @param element
	 * 			Estado del elemento
	 * @throws IllegalArgumentException
	 */
	public void setElementState (int i, int j, SquareState element) throws IllegalArgumentException {
		if (i >= GPSRun.N || i < 0 || j >= GPSRun.N || j < 0) {
			throw new IllegalArgumentException("Row or Column out of bounds!");
		}
		this.state[GPSRun.N * i + j] = element;
	}
	
	/**
	 * Devuelve el estado del elemento en la posicion (i,j)
	 * Lanza IllegalArgumentException si la coordenada no es valida
	 * 
	 * @param i
	 * 			Fila de la matriz
	 * @param j
	 * 			Columna de la matriz
	 * @return
	 * @throws IllegalArgumentException
	 */
	public SquareState getElementState (int i, int j) throws IllegalArgumentException {
		if (i >= GPSRun.N || i < 0 || j >= GPSRun.N || j < 0) {
			throw new IllegalArgumentException("Row or Column out of bounds!");
		}
		return this.state[GPSRun.N * i + j];
	}
	
	/**
	 * Devuelve la coordenada del espacio vacio
	 * 
	 * @return
	 */
	public Point getBlank () {
		int i = 0;
		for ( ; i < GPSRun.N*GPSRun.N; i++) {
			if (this.state[i] == null) {
				break;
			}
		}
		return new Point(i / GPSRun.N, i % GPSRun.N);
	}
	
	/**
	 * Esta funcion setea el blanco en la coordenada especificada.
	 * El metodo no chequea que ya haya un blanco, por lo tanto a la hora de setear la casilla como blanco 
	 * cerciorarse de que va a ser la unica en el tablero
	 * 
	 * @param i
	 * 			Fila de la matriz
	 * @param j
	 * 			Columna de la matriz
	 * @throws IllegalArgumentException
	 */
	public void setAsBlank (int i, int j) throws IllegalArgumentException {
		setElementState(i, j, null);
	}

	public boolean compare(GPSState state) {
		SquareState otherState, myState;
		for (int i = 0; i < GPSRun.N; i++) {
			for (int j = 0; j < GPSRun.N; j++) {
				otherState = state.getElementState(i, j);
				myState = this.getElementState(i, j);
				if (otherState == null && myState != null || myState == null && otherState != null ||
						otherState != null && myState != null && !otherState.equals(myState)) {
					return false;
				}
			}
		}
		return true;
	}

	public void copyState(GPSState state) {
		SquareState oldSquareState;
		for (int i = 0; i < GPSRun.N; i++) {
			for (int j = 0; j < GPSRun.N; j++) {
				oldSquareState = state.getElementState(i, j);
				if (oldSquareState != null) {
					this.state[i*GPSRun.N+j] = new SquareState(oldSquareState.getDirection(), oldSquareState.getFirstColor(), oldSquareState.getSndColor());
				} else {
					this.state[i*GPSRun.N+j] = null;
				}
			}
		}
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for (int i = 0; i < GPSRun.N*GPSRun.N-1; i++) {
			if (this.state[i] != null) {
				sb.append(this.state[i].toString() + ",");
			} else {
				sb.append("BLANK, ");
			}
		}
		if (this.state[GPSRun.N*GPSRun.N-1] != null) {
			sb.append(this.state[GPSRun.N*GPSRun.N-1].toString() + "]");
		} else {
			sb.append("BLANK]");
		}
		return sb.toString();
	}
	
	
	
	@Override
	public int hashCode() {
		final int prime = 31;
		//int result = 1;

		return prime*Arrays.hashCode(state);		
//		for(int i = 0 ; i < GPSRun.N*GPSRun.N ; i++){
//			result += prime*((state[i] == null)? 0: state[i].hashCode());
//	}
//		return result;
	}

	@Override
	public boolean equals(Object object) {
		if(object == null){
			return false;
		}

		if (this == object) {
			return true;
		}
		if (!(object instanceof GPSState)) {
			return false;
		}
		GPSState state = (GPSState) object;
		return simetric(state);
	}

	@Override
	public boolean simetric(GPSState state) {

		if(compare(state))
			return true;
		
		GPSState s = rotate90((GPSStateImplementation)state);
		for(int i = 0 ; i < 3 ; i++){
			if(compare(s))
				return true;
			s = rotate90((GPSStateImplementation)s);
		}
		
		return isMirror((GPSStateImplementation) state);
	}
	
	
	public boolean isMirror(GPSStateImplementation state){
		return compare(GPSStateImplementation.getAsMirror(Direction.HORIZONTAL, state)) 
		|| compare(GPSStateImplementation.getAsMirror(Direction.VERTICAL, state)); 
		
	}
	
	
	public static GPSStateImplementation rotate90(GPSStateImplementation orgState){
		
		GPSStateImplementation rotated = new GPSStateImplementation();
		
		for(int i = 0; i < GPSRun.N ; i++){
			for(int j = 0; j < GPSRun.N ; j++){
				SquareState sq = orgState.state[(GPSRun.N - (j+1))*GPSRun.N + i];
				if(sq != null) {
					rotated.state[i*GPSRun.N + j] = SquareState.rotate90(sq);
				} else {
					rotated.state[i*GPSRun.N + j] = null;
				}
			}
		}
		return rotated;
	}
	
	
	public static GPSStateImplementation getAsMirror(Direction dir, GPSStateImplementation orgState){
		
		SquareState[] states = new SquareState[GPSRun.N*GPSRun.N];
		
		if (dir == Direction.VERTICAL){	
			states[0*GPSRun.N + 0] = SquareState.mirror(dir, orgState.state[0*GPSRun.N + 2]);
			states[0*GPSRun.N + 1] = SquareState.mirror(dir, orgState.state[0*GPSRun.N + 1]);
			states[0*GPSRun.N + 2] = SquareState.mirror(dir, orgState.state[0*GPSRun.N + 0]);
			states[1*GPSRun.N + 0] = SquareState.mirror(dir, orgState.state[1*GPSRun.N + 2]);
			states[1*GPSRun.N + 1] = SquareState.mirror(dir, orgState.state[1*GPSRun.N + 1]);
			states[1*GPSRun.N + 2] = SquareState.mirror(dir, orgState.state[1*GPSRun.N + 0]);
			states[2*GPSRun.N + 0] = SquareState.mirror(dir, orgState.state[2*GPSRun.N + 2]);
			states[2*GPSRun.N + 1] = SquareState.mirror(dir, orgState.state[2*GPSRun.N + 1]);
			states[2*GPSRun.N + 2] = SquareState.mirror(dir, orgState.state[2*GPSRun.N + 0]);
		} else {
			states[0*GPSRun.N + 0] = SquareState.mirror(dir, orgState.state[2*GPSRun.N + 0]);
			states[0*GPSRun.N + 1] = SquareState.mirror(dir, orgState.state[2*GPSRun.N + 1]);
			states[0*GPSRun.N + 2] = SquareState.mirror(dir, orgState.state[2*GPSRun.N + 2]);
			states[1*GPSRun.N + 0] = SquareState.mirror(dir, orgState.state[1*GPSRun.N + 0]);
			states[1*GPSRun.N + 1] = SquareState.mirror(dir, orgState.state[1*GPSRun.N + 1]);
			states[1*GPSRun.N + 2] = SquareState.mirror(dir, orgState.state[1*GPSRun.N + 2]);
			states[2*GPSRun.N + 0] = SquareState.mirror(dir, orgState.state[0*GPSRun.N + 0]);
			states[2*GPSRun.N + 1] = SquareState.mirror(dir, orgState.state[0*GPSRun.N + 1]);
			states[2*GPSRun.N + 2] = SquareState.mirror(dir, orgState.state[0*GPSRun.N + 2]);
		}
		return new GPSStateImplementation(states);
	}
}
